Project Invisible Internet (hereinafter referred to as I2P) provides developers with a platform for developing applications with enhanced user privacy requirements. This is a virtual network on top of the regular Internet in which nodes can exchange data without revealing their real IP address. Instead of IP addresses within the Invisible Internet, connections occur between virtual addresses called I2P Destination. You can have as many such addresses as you like and change them even for each connection; they do not provide the other party with any information about the real IP address of the client.
This article describes the basic things you need to know to write I2P applications. Code examples are provided in Python using the built-in asynchronous framework asyncio.
Enabling SAM API and installing i2plib
I2P offers several APIs for interacting with client applications. For Java applications, I2CP is used; for regular client-server applications, you can use I2PTunnel, HTTP and Socks proxies. We will be making an application in Python, so we choose SAM. By default, the SAM API is disabled in the original Java client, so you need to enable it. Go to the I2P router web console, page "I2P internals" -> "Clients". Check "Run at Startup" and click "Start", then "Save Client Configuration"".
IN C++ i2pd client SAM is already enabled by default.
To make it easier to use the SAM API, I wrote a Python library i2plib. You can install it via pip or download the source code from GitHub.
pip install i2plib
Since this library works with built-in asynchronous framework asyncio, Keep in mind that the code examples are also taken from asynchronous functions (coroutines) that run in the event loop. There are additional examples of use in the repository.
Destination and session creation
At its core, I2P Destination is a keychain for data encryption and signing. Public keys from this keychain are published on the I2P network and are used instead of an IP address to create connections.
Let's generate i2plib.Destination, which we will use later:
dest = await i2plib.new_destination()
print(dest.base32 + ".b32.i2p") # вывод base32 адреса
base32 address is a hash by which other peers can find your Destination on the network. If you plan to use this Destination in your program on an ongoing basis, save the contents of dest.private_key.data to a local file.
Now you can create a SAM session, which literally means making this Destination online on the network:
session_nickname = "test-i2p" # каждая сессия должна иметь уникальный nickname
_, session_writer = await i2plib.create_session(session_nickname, destination=dest)
It is important to note here that Destination will be online as long as the session_writer socket is open. If you want to "turn off" this Destination from the network, call session_writer.close().
Making outgoing connections
Now that Destination is online, we can use it to communicate with other nodes. For example, we connect to the node "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p", send an HTTP GET request and read the response (the web server "i2p-projekt.i2p" is located there"):
remote_host = "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p"
reader, writer = await i2plib.stream_connect(session_nickname, remote_host)
writer.write("GET /en/ HTTP/1.0\nHost: {}\r\n\r\n".format(remote_host).encode())
buflen, resp = 4096, b""
while 1:
data = await reader.read(buflen)
if len(data) > 0:
resp += data
else:
break
writer.close()
print(resp.decode())
We accept incoming connections
When connecting to another host, as you can see, everything is simple, but there is one nuance with accepting incoming messages. When a new client connects to you, SAM sends an ASCII string with the client's Destination to the socket. Since Destination and data can come in one piece, you need to take this into account.
This is what a simple PING-PONG server looks like, which accepts an incoming connection, stores the client's Destination in the remote_destination variable, and sends back a PONG:
async def handle_client(incoming, reader, writer):
"""Обработка клиентского соединения"""
dest, data = incoming.split(b"\n", 1)
remote_destination = i2plib.Destination(dest.decode())
if not data:
data = await reader.read(BUFFER_SIZE)
if data == b"PING":
writer.write(b"PONG")
writer.close()
# Вечный цикл, который принимает клиентские соединения и запускает обработчик
while True:
reader, writer = await i2plib.stream_accept(session_nickname)
incoming = await reader.read(BUFFER_SIZE)
asyncio.ensure_future(handle_client(incoming, reader, writer))
More information
It describes the use of the Streaming protocol, which performs the functions of TCP/IP in an I2P network. The SAM API also provides the ability to send and receive anonymous datagrams, similar to the UDP protocol. This functionality is not yet available in i2plib and will be added later.
This is only the most basic information, but it is already enough to start your I2P project. The Invisible Internet is suitable for writing various applications in which it is primarily important to maintain user privacy. The network does not impose any restrictions on developers; these can be either client-server or P2P applications.